package com.zktravel.core.bean;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.odianyun.util.reflect.ReflectUtils;
import com.odianyun.util.value.ValueUtils;

@SuppressWarnings("unchecked")
public class DataArgs extends HashMap<String, Object> {
	private static final long serialVersionUID = 1L;
	
	private Map<String, String> aliasMap = Maps.newHashMap();
	
	public static DataArgs of(String key, Object value) {
		DataArgs args = new DataArgs();
		args.put(key, value);
		return args;
	}
	
	public static DataArgs of(String k1, Object v1, String k2, Object v2) {
		DataArgs args = new DataArgs();
		args.put(k1, v1);
		args.put(k2, v2);
		return args;
	}
	
	public DataArgs alias(String key, String alias) {
		aliasMap.put(key, alias);
		return this;
	}
	
	public Set<String> getKeysWithAliasForType(Class<?> beanClass) {
		String[] fields = ReflectUtils.getterNames(beanClass);
		List<String> fieldList = Arrays.asList(fields);
		
		Set<String> set = Sets.newHashSetWithExpectedSize(this.size());
		for (String key : this.keySet()) {
			if (! fieldList.contains(key)) {
				continue;
			}
			
			String alias = aliasMap.get(key);
			set.add(ValueUtils.ifNull(alias, key));
		}
		return set;
	}
	
	/**
	 * 根据传入的旧值Bean，移除未修改的Key
	 * @param oldBean
	 */
	public void removeUnmodifiedKeys(Object oldBean) {
		Iterator<String> keys = this.keySet().iterator();

		String[] fields = ReflectUtils.getterNames(oldBean.getClass());
		List<String> fieldList = Arrays.asList(fields);
		
		while (keys.hasNext()) {
			String key = keys.next();
			if (! fieldList.contains(key)) {
				continue;
			}
			
			Object newValue = this.get(key);
			Object oldValue = ReflectUtils.callGetMethod(oldBean, key);
			
			if (newValue == null && oldValue == null) {
				keys.remove();
				continue;
			}
			
			if (newValue != null) {
				String newValueStr = newValue.toString();
				if (oldValue != null) {
					if (newValueStr.equals(oldValue.toString())) {
						keys.remove();
						continue;
					}
				}
			}
		}
	}
	
	public Set<Entry<String, Object>> entrySetWithAlias() {
		Map<String, Object> map = Maps.newHashMapWithExpectedSize(this.size());
		
		for (Entry<String, Object> entry : this.entrySet()) {
			String alias = aliasMap.get(entry.getKey());
			if (alias == null) {
				map.put(entry.getKey(), entry.getValue());
			} else {
				map.put(alias, entry.getValue());
			}
		}
		
		return map.entrySet();
	}

	public <T> T get(String key, Class<T> typeClass) {
		Object value = this.get(key);
		if (value == null) return null; 
		
		return convert(value, typeClass);
	}
	
	public <T> T[] getArray(String key, Class<T> typeClass) {
		Object value = this.get(key);
    	if (value == null) return null;

		return convertArray(value, typeClass);
	}

    private <T> T convert(Object obj, Class<T> typeClass) {
    	return ValueUtils.convert(obj, typeClass);
    }
    
	private <T> T[] convertArray(Object obj, Class<T> typeClass) {
    	if (List.class.isAssignableFrom(obj.getClass())) {
    		List<?> list = (List<?>) obj;
    		return doConvertList(list, typeClass);
    	}
    	
    	if (obj.getClass().isArray()) {
    		Class<?> valueClass = obj.getClass().getComponentType();
    		if (typeClass.equals(valueClass)) {
    			return (T[]) obj;
    		}
        	Object[] array = (Object[]) obj;
    		return doConvertArray(array, typeClass);
    	}
    	
    	if (String.class.isAssignableFrom(obj.getClass())) {
    		String str = (String) obj;
        	Object[] array = str.split(",");
    		return doConvertArray(array, typeClass);
    	}
    	
    	throw new ClassCastException(obj.getClass() + " cannot be cast to " + typeClass);
    }
    

    private <T> T[] doConvertList(List<?> list, Class<T> typeClass) {
		T[] rslt = (T[]) Array.newInstance(typeClass, list.size());
		int idx = 0;
		for (Object item : list) {
			rslt[idx ++] = convert(item, typeClass);
		}
		return rslt;
    }
    
    private <T> T[] doConvertArray(Object[] array, Class<T> typeClass) {
		T[] rslt = (T[]) Array.newInstance(typeClass, array.length);
		int idx = 0;
		for (Object item : array) {
			rslt[idx ++] = convert(item, typeClass);
		}
		return rslt;
    }
    
}
